﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Linq;
using System.Data.SqlClient;
using System.Linq;
using CoreLibrary;
using CoreLibrary.Cache;
using CoreLibrary.Collection;
using CoreLibrary.Repo;
using IBBI.DataBase.Impl.Sql;
using IBBI.DataBase.Obj;
using IBBI.DataBase.Persistent;
using NLog;

namespace IBBI.DataBase.Impl {

    public class CategoryService : BaseRepository<Category>, ICategoryService {

        private static readonly Func<DataContext, int, Category>
          LinqGetById = CompiledQuery.Compile(
             (DataContext context, int id) =>
                 context.GetTable<Category>().SingleOrDefault(a => a.Id == id)
         );

        private static readonly Func<DataContext, string, Category>
          LinqGetByName = CompiledQuery.Compile(
             (DataContext context, string name) =>
                 context.GetTable<Category>().SingleOrDefault(a => a.Name == name)
        );

        private static readonly Func<DataContext, int, IQueryable<Category>>
           LinqGetCatalogsByUpper = CompiledQuery.Compile(
            (DataContext context, int level) =>
                context.GetTable<Category>().Where(a => a.Level <= level)
                                               .OrderBy(a => a.Id)
        );
        private static readonly Func<DataContext, int, IQueryable<Category>>
           LinqGetChildren = CompiledQuery.Compile(
            (DataContext context, int parentId) =>
                context.GetTable<Category>().Where(a => a.PID == parentId)
                                               .OrderBy(a => a.Id)
        );

        private static readonly Func<DataContext, int, int, IQueryable<Category>>
           LinqGetDescendants = CompiledQuery.Compile(
            (DataContext context, int from, int to) =>
                context.GetTable<Category>().Where(a => a.LID > from && a.LID <= to)
                                               .OrderBy(a => a.LID)
        );

        public ICache Cache { get; set; }

        private const string CatalogIDCacheKey       = "catalog_id_cache_key";
        private const string CatalogNameCacheKey     = "catalog_name_cache_key";
        private const string CatalogChildrenCacheKey = "catalog_children_cache_key";

        public override string TableName {
            get {
                return "CATEGORY";
            }
        }

        protected override void OnInit () {
            Asserts.NotNull(Cache, "Cache can not be null.");
            Asserts.NotBlank(TableName, "Table name can not be empty.");
        }

        protected override void OnStart () {
            GetOrCreateCache();
        }

        protected override void OnStop () {
            Cache.Remove(CatalogIDCacheKey);
            Cache.Remove(CatalogNameCacheKey);
            Cache.Remove(CatalogChildrenCacheKey);
        }

        protected override void OnDestroy () {
            Stop();
        }

        protected void GetOrCreateCache () {
            var catalogs = GetAll();
            Cache.Get(CatalogIDCacheKey,
                () => new ReadWriteDictionary<int, Category>(catalogs.ToDictionary(c => c.Id)),
                true
            );
            Cache.Get(CatalogNameCacheKey,
                () => new ReadWriteDictionary<string, Category>(catalogs.ToDictionary(c => c.Name)),
                true
            );
            Cache.Get(CatalogChildrenCacheKey,
                () => {
                    var dic = new ReadWriteDictionary<int, List<Category>>();
                    var items = GetCatalogByUpperLevel(2);
                    foreach (var item in items) {
                        var item1 = item;
                        dic.Put(item.Id, Execute(cxt => LinqGetChildren(cxt, item1.Id).ToList()));
                    }
                    return dic;
                },
                true
            );
        }

        protected ReadWriteDictionary<int, Category> GetOrCreateCacheById () {
            return Cache.Get(
                   CatalogIDCacheKey,
                   () => new ReadWriteDictionary<int, Category>(GetAll().ToDictionary(c => c.Id)),
                   true
            );
        }

        protected ReadWriteDictionary<string, Category> GetOrCreateCacheByName () {
            return Cache.Get(CatalogNameCacheKey,
                   () => new ReadWriteDictionary<string, Category>(GetAll().ToDictionary(c => c.Name)),
                   true
            );
        }

        protected ReadWriteDictionary<int, List<Category>> GetOrCreateChildrenCache () {
            return Cache.Get(
                   CatalogChildrenCacheKey,
                   () => {
                       var dic = new ReadWriteDictionary<int, List<Category>>();
                       var items = GetCatalogByUpperLevel(2);
                       foreach (var item in items) {
                           var item1 = item;
                           dic.Put(item.Id, Execute(cxt => LinqGetChildren(cxt, item1.Id).ToList()));
                       }
                       return dic;
                   },
                   true
            );
        }

        protected List<Category> GetCatalogByUpperLevel (int level) {
            return Execute(context => LinqGetCatalogsByUpper(context, level).ToList());
        }

        protected internal Category GetInternalCatalog (int catId) {
            return Execute(context => LinqGetById(context, catId));
        }

        protected internal Category GetInternalCatalog (string catName) {
            return Execute(context => LinqGetByName(context, catName));
        }

        public Category GetCategory (int catId) {
            var cache = GetOrCreateCacheById();
            if (!cache.ContainsKey(catId)) {
                var catalog = GetInternalCatalog(catId);
                if (catalog != null) {
                    cache[catId] = catalog;
                }
            }
            return cache.Get(catId);
        }

        public Category GetCategoryByName (string name) {
            var cache = GetOrCreateCacheByName();
            if (!cache.ContainsKey(name)) {
                var catalog = GetInternalCatalog(name);
                if (catalog != null) {
                    cache[name] = catalog;
                }
            }
            return cache.Get(name);
        }

        public Category GetRoot () {
            return Execute(cxt => LinqGetById(cxt, 1));
        }

        public Category GetParentCategory (int catId) {
            return Execute(cxt => {
                var current = LinqGetById(cxt, catId);
                return LinqGetById(cxt, Util.GetInt(current.PID, 0));
            });
        }

        public List<Category> GetPath (int catId) {
            return Execute(new SelectPath(catId, TableName));
        }

        public List<Category> GetChildren (int catId) {
            var cache = GetOrCreateChildrenCache();
            if (cache.ContainsKey(catId)) {
                return cache[catId];
            }

            return Execute(cxt => {
                var parent = LinqGetById(cxt, catId);
                if (parent == null) {
                    throw new Exception(string.Format("'{0}' not exist", catId));
                }
                return LinqGetChildren(cxt, catId).ToList();
            });
        }

        public List<Category> GetDescendants (int catId) {
            return Execute(cxt => {
                var cat = LinqGetById(cxt, catId);
                if (cat == null) {
                    throw new Exception(string.Format("'{0}' not exist", catId));
                }
                var cats = LinqGetDescendants(cxt, cat.LID, cat.RID);
                return cats.ToList();
            });
        }

        public Category UpdateCategory (UpdateCategoryRequest request) {
            return Execute((cxt, ts) => {
                var cat = LinqGetById(cxt, request.Id);
                if (cat == null) {
                    throw new Exception("category doesn't exist");
                }
                if (!string.IsNullOrEmpty(request.Thumb)) {
                    cat.Thumb = request.Thumb;
                }
                if (!string.IsNullOrEmpty(request.Description)) {
                    cat.Description = request.Description;
                }
                if (!string.IsNullOrEmpty(request.Title)) {
                    cat.Title = request.Title;
                }
                if (!string.IsNullOrEmpty(request.Keywords)) {
                    cat.Keywords = request.Keywords;
                }

                cxt.SubmitChanges();
                ts.Complete();
                return cat;
            });
        }

        public bool Synchronize () {
            Cache.Remove(CatalogIDCacheKey);
            Cache.Remove(CatalogNameCacheKey);
            Cache.Remove(CatalogChildrenCacheKey);
            GetOrCreateCache();
            return true;
        }

        public bool DeleteCategory (int id) {
            if (id == 0) {
                throw new Exception("root can not be deleted.");
            }

            var current = Execute(cxt => LinqGetById(cxt, id));
            if (current == null) {
                throw new Exception("item does not exist.");
            }

            if (!current.IsLeaf) {
                throw new Exception("Only leaf item can be deleted");
            }

            var width = current.RID - current.LID + 1;
            var query = new DeleteCategory(current.Id, width, current.RID, TableName);

            return Execute(context => {
                var sqlConnection  = (SqlConnection)context.Connection;
                var sqlTransaction = sqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);
                var sqlCommand = new SqlCommand(query.GetSQL(), sqlConnection, sqlTransaction) {
                    CommandType = CommandType.Text
                };

                try {
                    query.SetParameters(sqlCommand);
                    sqlCommand.ExecuteNonQuery();
                    sqlTransaction.Commit();
                    return true;
                } catch (Exception exception) {
                    sqlTransaction.Rollback();
                    LogManager.GetCurrentClassLogger().ErrorException(exception.Message,
                        exception
                    );
                    return false;
                } finally {
                    sqlCommand.Dispose();
                }

            });
        }

        public Category AppendCategory (int id, Category catalogItem) {

            var parent = Execute(cxt => LinqGetById(cxt, id));

            if (parent == null) {
                throw new Exception("Parent not exist.");
            }

            var cat = Execute(ctx => LinqGetByName(ctx, catalogItem.Name));
            if (cat != null) {
                throw new Exception("Catalog Name Already Exist.");
            }

            catalogItem.PID = parent.Id;
            catalogItem.LID = parent.IsLeaf ? parent.LID + 1 : parent.RID;
            catalogItem.RID = parent.IsLeaf ? parent.LID + 2 : parent.RID + 1;
            catalogItem.Level = parent.Level + 1;

            var updator = new UpdateCategory(TableName, parent.LID, parent.RID, catalogItem, parent.IsLeaf);
            Execute(context => {
                var sqlConnection = (SqlConnection)context.Connection;
                var sqlTransaction = sqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);
                var sqlCommand = new SqlCommand(updator.GetSQL(), sqlConnection, sqlTransaction) {
                    CommandType = CommandType.Text
                };

                try {
                    updator.SetParameters(sqlCommand);
                    sqlCommand.ExecuteNonQuery();
                    sqlTransaction.Commit();
                    return true;
                } catch (Exception exception) {
                    sqlTransaction.Rollback();
                    LogManager.GetCurrentClassLogger().ErrorException(exception.Message,
                        exception
                    );
                    throw;
                } finally {
                    sqlCommand.Dispose();
                }
            });

            return GetCategoryByName(catalogItem.Name);
        }

        public bool InsertCategoryAfter (int catId, Category catalogItem) {
            var catalog = Execute(cxt => LinqGetById(cxt, catId));
            if (catalog == null) {
                throw new Exception("Catalog not exist.");
            }

            catalogItem.PID = catalog.PID;
            catalogItem.LID = catalog.LID + 1;
            catalogItem.RID = catalog.LID + 2;
            catalogItem.Level = catalog.Level;

            var updator = new InsertCategoryAfter(catalog.RID, TableName, catalogItem);
            return Execute(context => {
                var sqlConnection = (SqlConnection)context.Connection;
                var sqlTransaction = sqlConnection.BeginTransaction(IsolationLevel.ReadCommitted);
                var sqlCommand = new SqlCommand(updator.GetSQL(), sqlConnection, sqlTransaction) {
                    CommandType = CommandType.Text
                };

                try {
                    updator.SetParameters(sqlCommand);
                    sqlCommand.ExecuteNonQuery();
                    sqlTransaction.Commit();
                    return true;
                } catch (Exception exception) {
                    sqlTransaction.Rollback();
                    LogManager.GetCurrentClassLogger().ErrorException(exception.Message,
                        exception
                    );
                    throw;
                } finally {
                    sqlCommand.Dispose();
                }

            });

        }
    }

}